#!/usr/bin/perl

package Ldif::Parser;

use warnings;
use strict;
use Data::Dumper;
use Class::Std;

{
    my %parsed_files : ATTR( :get<parsed_files> :set<parsed_files>);
    my %olc_object_classes : ATTR( :get<olc_object_classes> :set<olc_object_classes>);
    my %parsed_classes : ATTR( :get<parsed_classes> :set<parsed_classes>);

    sub BUILD {
        my ( $self, $obj_ID, $arg_ref ) = @_;

    }

    sub parse_file {
        my ( $self, $filename ) = @_;

        my $parsed_files = $self->get_parsed_files;
        return if exists $parsed_files->{$filename};

        open my $f, '<', $filename or die "error opening $filename: $!";
        my $schema = do { local $/; <$f> };
        $schema =~ s/^(#.*)|(\s*)$//mg;

        my (@parsed_arr) = $schema =~ m/
                olcObjectClasses: \s*
                  \(
                             \s*         (.*?)               \s+
                    (?: NAME \s+       ' (.*?) '             \s+ )?
                    (?: DESC \s+       ' (.*?) '             \s+ )?
                    (?: SUP  \s* \(? \s* (.*?)    \s* \)?    \s+ )?
                            (ABSTRACT|STRUCTURAL|AUXILIARY)? \s*
                    (?: MUST \s* \(? \s* (.*?)    \s* \)?    \s+ )?
                    (?: MAY  \s* \(? \s* (.*?)    \s* \)?    \s+ )?
                  \)
            /sgx;

        my $olc_object_classes = $self->get_olc_object_classes;

        for ( my $i = 0; $i < $#parsed_arr; $i += 7 ) {
            my $name = '';
            $name = $parsed_arr[ $i + 1 ] if defined $parsed_arr[ $i + 1 ];
            $olc_object_classes->{$name}->{name}       = $name;
            $olc_object_classes->{$name}->{numericoid} = $parsed_arr[$i] if defined $parsed_arr[$i];
            $olc_object_classes->{$name}->{desk}       = $parsed_arr[ $i + 2 ] if defined $parsed_arr[ $i + 2 ];
            $olc_object_classes->{$name}->{type}       = $parsed_arr[ $i + 4 ] if defined $parsed_arr[ $i + 4 ];            
            $olc_object_classes->{$name}->{file}       = $filename;

            if ( defined $parsed_arr[ $i + 3 ] ) {
                ( my $sup = $parsed_arr[ $i + 3 ] ) =~ s/\s//g;
                my @sup = split( '\$', $sup );

                for (@sup) {
                    $olc_object_classes->{$name}->{sup}->{$_} = '' unless $_ eq 'top';
                }
            }

            if ( defined $parsed_arr[ $i + 5 ] ) {
                ( my $must = $parsed_arr[ $i + 5 ] ) =~ s/\s//g;
                my @must = split( '\$', $must );

                $olc_object_classes->{$name}->{must}->{$_} = '' foreach @must;
            }

            if ( defined $parsed_arr[ $i + 6 ] ) {
                ( my $may = $parsed_arr[ $i + 6 ] ) =~ s/\s//g;
                my @may = split( '\$', $may );

                $olc_object_classes->{$name}->{may}->{$_} = '' foreach @may;
            }
        }

        $parsed_files->{$filename} = 1;
        $self->set_parsed_files($parsed_files);
        $self->set_olc_object_classes($olc_object_classes);
    }

    sub get_object_classes_names {
        my ($self) = @_;

        my $classes = $self->get_olc_object_classes;
        return keys(%$classes);
    }

    sub clear_object_classes {
        my ($self) = @_;

        $self->set_parsed_files(undef);
        $self->set_olc_object_classes(undef);

    }

    sub get_object_class_by_name {
        my ( $self, $name ) = @_;

        my $classes = $self->get_olc_object_classes;
        return $classes->{$name};
    }

    sub parse_object_classes {
        my ($self) = @_;

        my $classes = $self->get_olc_object_classes;
        return unless defined $classes;

    REDO:
        for my $class ( keys(%$classes) ) {
            my @sup_keys = keys %{ $classes->{$class}->{sup} };

            if ( @sup_keys > 0 ) {
                for my $sup_class (@sup_keys) {
                    $classes->{$class}->{must}->{$_} = '' foreach keys %{ $classes->{$sup_class}->{must} };
                    $classes->{$class}->{may}->{$_}  = '' foreach keys %{ $classes->{$sup_class}->{may} };
                    $classes->{$class}->{sup}->{$_}  = '' foreach keys %{ $classes->{$sup_class}->{sup} };

                    delete $classes->{$class}->{sup}->{$sup_class};
                }

                redo REDO;
            }
        }

        for my $class ( keys(%$classes) ) {
            delete $classes->{$class}->{sup};
        }

        $self->set_parsed_classes($classes);
    }

    sub get_parsed_object_class_by_name {
        my ( $self, $name ) = @_;

        my $classes = $self->get_parsed_classes;
        return $classes->{$name};
    }
}

1;
